package core;


import java.awt.image.*;
import java.awt.Color;
import java.util.*;
import gui.*;
import utility.*;

/**
 * @author daniele
 *
 */
public class TextureSynthetizer {
    
        private int _wIntorno = 25; //Intorni 5x5
    
        private BufferedImage _input_image;
        private BufferedImage _output_image;
        private List _points_xstar;
        private int _num_step;
        private Vector <Neighborhood> _intorniImmagineInput; //Per ogni p in X* esiste un intorno vicino in Zp
        private Vector <Neighborhood> _insiemeXp; //Equivale l'insieme
        private ClusteringTree _tree;
        private float _percentage = 0.0f;
        
        
        
        public TextureSynthetizer(int step, int wIntorno, BufferedImage inIm, BufferedImage outIm)
        {
                _num_step = step;
                _input_image = inIm;
                _output_image = outIm;
                _wIntorno = wIntorno;
                
                
                _intorniImmagineInput = new Vector<Neighborhood>();
                //Popolo l'insieme di intorni dell'immagine di input
                //Questo insieme non deve essere più toccato
                for(int i=_wIntorno/2; i<_input_image.getWidth()-_wIntorno/2; i+=_wIntorno/4)
                    for(int j=_wIntorno/2; j<_input_image.getHeight()-_wIntorno/2; j+=_wIntorno/4)
                    {
                        _intorniImmagineInput.addElement(new Neighborhood(_wIntorno, j, i));
                    }
                System.gc();
                //Inizializzo e popolo l'insieme di intorni dell'immagine di output
                _insiemeXp = new Vector <Neighborhood>();
                for(int i=_wIntorno/2; i<_output_image.getWidth()-_wIntorno/2; i = i+_wIntorno/4)
                    for(int j=_wIntorno/2; j<_output_image.getHeight()-_wIntorno/2; j=j+_wIntorno/4)
                {
                    _insiemeXp.addElement(new Neighborhood(_wIntorno, j, i));
                }
                System.gc();
                //Creo l'albero contenente gli intorni di input e usato al M-Step
                _tree = new ClusteringTree();
                _tree.setImg(_input_image);
                _tree.buildTree(4, _intorniImmagineInput);
                
        }
        
        private double calculatePotEnergy(Neighborhood xp, Neighborhood zp)
        {
                double value = 0.0;                
                int[][] values1 = zp.getLuminanceFromImage(_input_image);
                int[][] values2 = xp.getLuminanceFromImage(_output_image);
                for (int i=0; i<xp.getWidth(); i++)
                    for(int j=0; j<xp.getWidth(); j++)
                    {
                        //Al momento lo calcolo soltanto su un canale
                        value = value + (Math.pow((float)values1[i][j]/256 - (float)values2[i][j]/256, 2));
                    }
                return Math.sqrt(value);
                //return value;
        }
        
        private double calculateTotalEnergy(Vector x, Vector z)
        {
            double sum = 0.0;
            for(int i=0; i<x.size(); i++)
            {
                sum = sum + calculatePotEnergy((Neighborhood)x.elementAt(i), (Neighborhood)z.elementAt(i));
            }
            return sum;
        }
        
        private void eStep(Vector zp, Vector xp)
        {   
            //Passo di minimizzazione
            Neighborhood intornoZp = null;
            Neighborhood intornoXp = null;
            int[][] redValues, greenValues, blueValues;
            short[][][] redArrayToMerge = new short[_output_image.getHeight()][_output_image.getWidth()][xp.size()];
            short[][][] greenArrayToMerge = new short[_output_image.getHeight()][_output_image.getWidth()][xp.size()];
            short[][][] blueArrayToMerge = new short[_output_image.getHeight()][_output_image.getWidth()][xp.size()];
            for(int y=0; y<_output_image.getHeight();y++)
                for(int x=0; x<_output_image.getWidth(); x++)
                    for(int i=0; i<xp.size(); i++)
                    {
                        redArrayToMerge[y][x][i] = -1;
                        greenArrayToMerge[y][x][i] = -1;
                        blueArrayToMerge[y][x][i] = -1;
                    }
            for(int i=0; i<xp.size(); i++)
            {
                intornoZp = (Neighborhood)zp.elementAt(i);
                intornoXp = (Neighborhood)xp.elementAt(i);
                
                redValues = intornoZp.getRedValuesFromImage(_input_image);
                greenValues = intornoZp.getGreenValuesFromImage(_input_image);
                blueValues = intornoZp.getBlueValuesFromImage(_input_image);
                
                for(int y=0; y<redValues.length;y++)
                    for(int x=0; x<redValues[0].length; x++)
                    {
                        redArrayToMerge[intornoXp.getOriginY()+y][intornoXp.getOriginX()+x][i] = (short)redValues[y][x];
                        greenArrayToMerge[intornoXp.getOriginY()+y][intornoXp.getOriginX()+x][i] = (short)greenValues[y][x];
                        blueArrayToMerge[intornoXp.getOriginY()+y][intornoXp.getOriginX()+x][i] = (short)blueValues[y][x];
                    }

            }
            
            //Array di colori sullo stesso pixel
            
            Vector redValuesVector = new Vector();
            Vector greenValuesVector = new Vector();
            Vector blueValuesVector = new Vector();
            
            int[] pixelRedValues, pixelGreenValues, pixelBlueValues;
            
            for(int y=0; y<_output_image.getHeight();y++)
                for(int x=0; x<_output_image.getWidth(); x++)
                {
                    redValuesVector.clear();
                    greenValuesVector.clear();
                    blueValuesVector.clear();
                    
                    int sumRed = 0,
                        sumGreen = 0,
                        sumBlue = 0;
                    for(int i=0; i<xp.size(); i++)
                    {
                        if(redArrayToMerge[y][x][i]!=-1)
                        {
                            redValuesVector.add(new Integer(redArrayToMerge[y][x][i]));
                            greenValuesVector.add(new Integer(greenArrayToMerge[y][x][i]));
                            blueValuesVector.add(new Integer(blueArrayToMerge[y][x][i]));
                        }
                    }
                    
                    pixelRedValues = new int[redValuesVector.size()];
                    pixelGreenValues = new int[greenValuesVector.size()];
                    pixelBlueValues = new int[blueValuesVector.size()];
                    for(int index=0; index<redValuesVector.size(); index++)
                    {
                        pixelRedValues[index] = ((Integer)redValuesVector.elementAt(index)).intValue();
                        pixelGreenValues[index] = ((Integer)greenValuesVector.elementAt(index)).intValue();
                        pixelBlueValues[index] = ((Integer)blueValuesVector.elementAt(index)).intValue();
                    }
                    
                    sumRed = Utility.getMedianValue(pixelRedValues);
                    sumGreen = Utility.getMedianValue(pixelGreenValues);
                    sumBlue = Utility.getMedianValue(pixelBlueValues);
                    
                    _output_image.setRGB(y, x, new Color(sumRed, sumGreen, sumBlue).getRGB());
                }
        }
        
        private Vector <Neighborhood> mStep(Vector xp)
        {
            Vector <Neighborhood> zp = new Vector<Neighborhood>();
            Iterator iter = xp.iterator();
            while(iter.hasNext())
            {
            	Neighborhood current = (Neighborhood)iter.next();
            	zp.add(_tree.findClosest(current, _output_image));
            }
            return zp;
        }
        
        private Vector <Neighborhood> dummyMStep(Vector xp)
        {
            int minEnergyIndex = 0;
            double minEnergy = 0.0;
            double energy = 0.0;
            Vector <Neighborhood> zp = new Vector <Neighborhood>();
            Neighborhood currentXp = null;
            //Trovo l'intorno più vicino a ciascun elemento in xp in _input_image
            for(int s=0; s<xp.size(); s++)
            {
                currentXp = (Neighborhood)xp.elementAt(s);
                minEnergyIndex = 0;
                minEnergy = calculatePotEnergy(currentXp, (Neighborhood)_intorniImmagineInput.elementAt(0));
                for(int i=1; i<_intorniImmagineInput.size(); i++)
                {
                    energy = calculatePotEnergy(currentXp, (Neighborhood)_intorniImmagineInput.elementAt(i));
                    if(energy<minEnergy)
                    {
                        minEnergy = energy;
                        minEnergyIndex = i;
                    }
                }
                
                zp.add((Neighborhood)_intorniImmagineInput.elementAt(minEnergyIndex));
            }
            return zp;
        }
        
        // L'energia di un intorno sintetizzato è definita
        // come la distanza dal più vicino intorno dell'input
        // l'energia totale è la somma di tutti gli intorni
        public BufferedImage synthetize()
        {
            //La prima associazione avviene in maniera casuale
            Vector <Neighborhood> zp = new Vector<Neighborhood>();
            double prevEnergy = 0.0;
            double energy = 0.0;
            for(int i=0; i<_insiemeXp.size(); i++)
            {
                zp.addElement(_intorniImmagineInput.elementAt( (int)(Math.random()*_intorniImmagineInput.size()) ));
            }
            System.out.println("Energia totale iniziale: "+calculateTotalEnergy(_insiemeXp, zp));
            for (int s=0; s < _num_step; s++)
            {
                eStep(zp, _insiemeXp);
                
                zp = mStep(_insiemeXp);
                energy = calculateTotalEnergy(_insiemeXp, zp);
                if((int)energy == (int)prevEnergy)
                    return null;
                else
                    prevEnergy = energy;
                System.out.println("Energia totale dopo passo M: "+energy);
            }

            
            return null; //Deve ritornare l'immagine finale alla fine del processo di sintesi
        }
        
        /**
         * Ritorna l'indice dell'intorno. Utile per andare a pescare il corrispettivo in Zp
         */
        private int getNeighborhoodByCenter(Vector v, int x, int y)
        {
            Neighborhood intorno = null;
            
            for(int i=0; i<v.size(); i++)
            {
               intorno = (Neighborhood)v.elementAt(i);
               if(intorno.getCenterX() == x && intorno.getCenterY() == y)
                   return i;
            }
            
            return -1;
        }
        /**
         * 
         * @param zp Intorno zp
         * @param xp Intorno xp
         * @param index1 indice del primo intorno con cui fare il blending
         * @param index2 indice del secondo intorno
         */
        private void blendNeighborhood(Neighborhood current, Neighborhood next, Neighborhood zpNearNext)
        {
            int[][] redvalues1, greenvalues1, bluevalues1;
            int[][] redvalues2, greenvalues2, bluevalues2;
            
            
            redvalues1 = current.getRedValuesFromImage(_output_image);
            redvalues2 = zpNearNext.getRedValuesFromImage(_input_image);
            greenvalues1 = current.getGreenValuesFromImage(_output_image);
            greenvalues2 = zpNearNext.getGreenValuesFromImage(_input_image);
            bluevalues1 = current.getBlueValuesFromImage(_output_image);
            bluevalues2 = zpNearNext.getBlueValuesFromImage(_input_image);
            
            int distX = Math.abs(current.getCenterX() - next.getCenterX());
            int distY = Math.abs(current.getCenterY() - next.getCenterY());
            
            int red = 0;
            int green = 0;
            int blue = 0;
            
            int centerX = current.getCenterX();
            int centerY = current.getCenterY();
            
            if(distX == 0) //Siamo nel caso in basso
            {
               for(int i=0; i<_wIntorno/2; i++)//Righe
                   for(int j=0; j<_wIntorno; j++)//Colonne
                   {
                       red = (redvalues1[_wIntorno/2+1+i][j] + redvalues2[i][j])/2;
                       green = (greenvalues1[_wIntorno/2+1+i][j] + greenvalues2[i][j])/2;
                       blue = (bluevalues1[_wIntorno/2+1+i][j] + bluevalues2[i][j])/2;
                       
                       _output_image.setRGB(centerX+i, centerY-_wIntorno/2+j, new Color(red, green, blue).getRGB());
                   }
            }
            else if(distY == 0)//Siamo nel caso a destra
            {
                for(int i=0; i<_wIntorno; i++)//Righe
                   for(int j=0; j<_wIntorno/2; j++)//Colonne
                   {
                       red = (redvalues1[i][_wIntorno/2+1+j] + redvalues2[i][j])/2;
                       green = (greenvalues1[i][_wIntorno/2+1+j] + greenvalues2[i][j])/2;
                       blue = (bluevalues1[i][_wIntorno/2+1+j] + bluevalues2[i][j])/2;
                       
                       _output_image.setRGB(centerX-_wIntorno/2+i, centerY+j, new Color(red, green, blue).getRGB());
                   }
            }
            else if(distX == distY) //Siamo nel caso diagonale
            {
                for(int i=0; i<_wIntorno/2; i++)//Righe
                   for(int j=0; j<_wIntorno/2; j++)//Colonne
                   {
                       red = (redvalues1[_wIntorno/2+1+i][_wIntorno/2+1+j] + redvalues2[i][j])/2;
                       green = (greenvalues1[_wIntorno/2+1+i][_wIntorno/2+1+j] + greenvalues2[i][j])/2;
                       blue = (bluevalues1[_wIntorno/2+1+i][_wIntorno/2+1+j] + bluevalues2[i][j])/2;
                       
                       _output_image.setRGB(centerX+i, centerY+j, new Color(red, green, blue).getRGB());
                   }
            }
        }
        
        
        
        public BufferedImage getOutputImage()
        {
            return _output_image;
        }
        
        public void dispose()
        {
            _points_xstar = null;
            _intorniImmagineInput = null; //Per ogni p in X* esiste un intorno vicino in Zp
            _insiemeXp = null; //Equivale l'insieme
            _tree = null;
            System.gc();
        }
        
        public float getPercentage()
        {
            return _percentage;
        }
        
        public void reinitialize(int step, int wIntorno, BufferedImage inIm, BufferedImage outIm)
        {
                _num_step = step;
                _input_image = inIm;
                _output_image = outIm;
                _wIntorno = wIntorno;
                
                
                _intorniImmagineInput = new Vector<Neighborhood>();
                //Popolo l'insieme di intorni dell'immagine di input
                //Questo insieme non deve essere più toccato
                for(int i=_wIntorno/2; i<_input_image.getWidth()-_wIntorno/2; i+=_wIntorno/4)
                    for(int j=_wIntorno/2; j<_input_image.getHeight()-_wIntorno/2; j+=_wIntorno/4)
                    {
                        _intorniImmagineInput.addElement(new Neighborhood(_wIntorno, j, i));
                    }
                System.gc();
                //Inizializzo e popolo l'insieme di intorni dell'immagine di output
                _insiemeXp = new Vector <Neighborhood>();
                for(int i=_wIntorno/2; i<_output_image.getWidth()-_wIntorno/2; i = i+_wIntorno/4)
                    for(int j=_wIntorno/2; j<_output_image.getHeight()-_wIntorno/2; j=j+_wIntorno/4)
                {
                    _insiemeXp.addElement(new Neighborhood(_wIntorno, j, i));
                }
                System.gc();
                //Creo l'albero contenente gli intorni di input e usato al M-Step
                _tree = new ClusteringTree();
                _tree.setImg(_input_image);
                _tree.buildTree(4, _intorniImmagineInput);
                System.gc();
        }
}
